﻿module game.battle.player;

import gamelib.types;
import gamelib.queue;
import gamelib.graphics.renderer2d.renderer;
import gamelib.graphics.renderer2d.textureview;

import game.battle.battlescene;
import game.units;
import game.sprite;
import game.battle.entity;
import game.controls;

import game.battle.projectiles.playerprojectiles;
import game.battle.projectiles.fireprojectiles;

import game.battle.weapons.baseweapon;

final class Player : Entity
{
private:
    BattleScene mWorld;
    Sprite mSprite;

    void spawnProjectile(T)(in T projectile)
    {
        mWorld.spawnProjectile(projectile);
    }

    BaseWeapon!(5, cast(scalar_t)10, SimplePlayerProjectile) mWeapon;

    bool mBoost = false;
    static immutable maxBoostCharge = 50;
    static immutable maxBoostCooldown = 50;
    int mBoostCharge = 0;
    int mBoostCooldown = 0;
    void checkBoost()
    {
        if(mBoostCooldown > 0)
        {
            --mBoostCooldown;
            --mBoostCharge;
            mBoostCharge = max(0, mBoostCharge);
            mBoost = false;
            return;
        }

        mBoost = mWorld.controlsState.boost;
        if(mBoost)
        {
            ++mBoostCharge;
        }
        else
        {
            --mBoostCharge;
            mBoostCharge = max(0, mBoostCharge);
        }

        if(mBoostCharge >= maxBoostCharge)
        {
            mBoostCooldown = maxBoostCooldown;
            mBoost = false;
        }
    }

    int mHealth = 10;

    int mKilled = 0;
    int mCurrentCombo = 0;
    int mComboTimer = 0;
    enum MaxCombo = 50;
    enum MaxComboTimer = 100;
public:
    static immutable scalar_t acceleration = div(1,2);
    static immutable scalar_t deceleration = acceleration / 2;
    static immutable scalar_t rotAcceleration = div(5,1000);
    static immutable scalar_t rotDeceleration = rotAcceleration / 2;
    static immutable rot_t    rotAccelerationV = rot_t(cos(rotAcceleration), sin(rotAcceleration));
    static immutable rot_t    rotDecelerationV = rot_t(cos(rotDeceleration), sin(rotDeceleration));
    static immutable scalar_t maxRotSpeed = div(5,100);
    static immutable rot_t    maxRotSpeedV = rot_t(cos(maxRotSpeed), sin(maxRotSpeed));
    static immutable scalar_t baseMaxSpeed = 10;
    static immutable scalar_t boostCoeff = 2;
    @property auto maxSpeed() const pure nothrow { return boost ? baseMaxSpeed * boostCoeff : baseMaxSpeed; }
    @property auto maxSpeedSquared() const pure nothrow { return (cast(scalarw_t)maxSpeed) ^^ 2; }

    static immutable scalarw_t radius = 16;
    static immutable radiusSq = radius * radius;

    enum deathAnimLength = 25;

    @property auto up()     const pure nothrow { return mWorld.controlsState.up && alive; }
    @property auto down()   const pure nothrow { return mWorld.controlsState.down && alive; }
    @property auto left()   const pure nothrow { return mWorld.controlsState.left && alive; }
    @property auto right()  const pure nothrow { return mWorld.controlsState.right && alive; }
    @property auto attack() const pure nothrow { return mWorld.controlsState.attack && alive; }
    @property auto boost()  const pure nothrow { return mBoost && alive; }
    @property auto strafe() const pure nothrow { return attack; }

    @property auto health() const pure nothrow { return mHealth > 0 ? mHealth : 0; }
    @property void health(int h) pure nothrow { mHealth = h; }

    @property bool alive() const pure nothrow { return health > 0; }

    @property auto killed() const pure nothrow { return mKilled; }
    void onKilled(T)(in T ent)    pure nothrow
    {
        ++mKilled;
        mComboTimer = MaxComboTimer;
        if(mCurrentCombo < MaxCombo)
        {
            ++mCurrentCombo;
        }
    }

    @property auto currentCombo() const pure nothrow { return mCurrentCombo; }

    this(BattleScene w)
    {
        mSprite = w.textureManager.texture!"ship";
        mWorld = w;
    }

    void process()
    {
        if(mComboTimer > 0)
        {
            if(0 == --mComboTimer)
            {
                mCurrentCombo = 0;
            }
        }
        checkBoost();
        update!false(this);
        if(mHealth > 0)
        {
            mWeapon.update();
            if(attack)
            {
                mWeapon.fire(this, pos, speed, dir);
            }

            if(boost)
            {
                import std.random;
                SimpleFireProjectile proj;
                proj.pos = pos - dir * cast(scalar_t)radius + pos_t(cast(scalar_t)uniform(-2,2),
                                                                    cast(scalar_t)uniform(-2,2));
                proj.speed = speed - dir * cast(scalar_t)5;
                proj.size = 2;
                spawnProjectile(proj);

            }
        }
        else if(mHealth > -deathAnimLength)
        {
            if(0 == mHealth % 2)
            {
                import std.random;
                ExpandingFireProjectile proj;
                proj.pos = pos + pos_t(cast(scalar_t)uniform(-10,10),
                                       cast(scalar_t)uniform(-10,10));
                proj.speed = speed * div(4,5);
                proj.size = 2;
                spawnProjectile(proj);
            }
            --mHealth;
        }
    }

    void draw(Renderer ren, in Point pt)
    {
        if(mHealth > -deathAnimLength)
        {
            mSprite.draw(ren, pt);
        }
    }
}

